//#include <algorithm>
#include <sstream>
#include <cstdio>

#include <glm/glm.hpp>

#include "Bloom.h"
#include "RenderContex.h"
#include "RenderEngine.h"

#include "Debug.h"

using namespace std;

using glm::vec2;
using glm::vec3;

Shader Bloom::shader_glare_detection;
Shader Bloom::shader_blur;
Shader Bloom::shader_combine;
//Shader Bloom::shader_tonemap;


void Bloom::init(ToneMapOperator toneMapOperator, float exposure, float bloomThreshold, float bloomWidth, float bloomIntensity, float defocus)
{

	this->toneMapOperator = toneMapOperator;
	this->exposure = exposure;
	this->burnout = numeric_limits<float>::infinity();
	this->bloomThreshold = bloomThreshold;
	this->bloomWidth = bloomWidth;
	this->bloomIntensity = bloomIntensity;
	this->defocus = defocus;

	int w = RenderContex::window_width;
	int h = RenderContex::window_height;
	resize(w, h);
}

void Bloom::resize(int width, int height)
{
	glareRT.init(width / 2, height / 2, GL_SRGB8_ALPHA8);

	int base = 2;
	for (int i = 0; i < N_PASSES; i++)
	{
		tmpRT[i][0].init(max(width / base, 1), max(height / base, 1), GL_SRGB8_ALPHA8);
		tmpRT[i][1].init(max(width / base, 1), max(height / base, 1), GL_SRGB8_ALPHA8);
		base *= 2;
	}
}

void Bloom::render(RenderTexture *src, RenderTexture *dst)
{
	if (bloomIntensity > 0.0f) 
	{
		glareDetection(*src);
		//glareRT.render_to_screen();

		RenderTexture* current = &glareRT;
		for (int i = 0; i < N_PASSES; i++)
		{
			blur(current, &tmpRT[i][0], vec2(1.0f, 0.0f));		// horizontal
			blur(&tmpRT[i][0], &tmpRT[i][1], vec2(0.0, 1.0f));	// vertical
			
			current = &tmpRT[i][1];
		}

		combine(src, dst);
	}
	else
	{
		//toneMap(src, dst);
        Debug::LogError("ERROR, no toneMap");
	}
}


void Bloom::glareDetection(RenderTexture & src)
{
	int w = glareRT.width();
	int h = glareRT.height();
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);

	vec2 pixelSize = vec2(1.0f / w, 1.0f / h);
	shader_glare_detection.use();
	shader_glare_detection.bind_vec2("pixelSize", pixelSize);
	shader_glare_detection.bind_float("exposure", exposure);
	shader_glare_detection.bind_float("bloomThreshold", bloomThreshold);
	shader_glare_detection.bind_texture("finalTex", src, 0);

	RenderEngine::set_render_target(&glareRT, nullptr);
	ModelManager::screen_aligned_quad.render();
	RenderEngine::set_render_target(nullptr, nullptr);
}

void Bloom::blur(RenderTexture * src, RenderTexture * dst, glm::vec2 direction)
{
	int w = dst->width();
	int h = dst->height();
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);

	vec2 pixelSize = vec2(1.0f / w, 1.0f / h);
	vec2 step = pixelSize * bloomWidth * direction;
	shader_blur.use();
	shader_blur.bind_texture("srcTex", *src, 0);
	shader_blur.bind_vec2("step", step);

	RenderEngine::set_render_target(dst, nullptr);
	ModelManager::screen_aligned_quad.render();
	RenderEngine::set_render_target(nullptr, nullptr);
}

void Bloom::combine(RenderTexture *src, RenderTexture *dst)
{
	int w = RenderContex::window_width;
	int h = RenderContex::window_height;
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);

	vec2 pixelSize = vec2(1.0f / w, 1.0f / h);

	shader_combine.use();
	shader_combine.bind_vec2("pixelSize", pixelSize);
	shader_combine.bind_float("exposure", exposure);
	//shader_combine.bind_float("burnout", burnout);
	//shader_combine.bind_float("bloomThreshold", bloomThreshold);
	//shader_combine.bind_float("bloomWidth", bloomWidth);
	shader_combine.bind_float("bloomIntensity", bloomIntensity);
	shader_combine.bind_float("defocus", defocus);
	shader_combine.bind_texture("finalTex", *src, 0);

	char name_buffer[16];
	for (int i = 0; i < N_PASSES; i++)
	{
		sprintf(name_buffer, "srcTex[%d]", i);
		shader_combine.bind_texture(name_buffer, tmpRT[i][1], i + 1);
	}

	RenderEngine::set_render_target(dst, nullptr);
	ModelManager::screen_aligned_quad.render();
	RenderEngine::set_render_target(nullptr, nullptr);
}


//void Bloom::toneMap(RenderTexture * src, RenderTexture *dst) {
//
//	glViewport(0, 0, RenderContex::window_width, RenderContex::window_height);
//
//	shader_tonemap.use();
//
//	shader_combine.bind_float("exposure", exposure);
//	shader_combine.bind_float("burnout", exposure);
//	shader_combine.bind_float("bloomThreshold", exposure);
//	shader_combine.bind_float("bloomWidth", exposure);
//	shader_combine.bind_float("bloomIntensity", exposure);
//	shader_combine.bind_float("defocus", exposure);
//	shader_combine.bind_texture("finalTex", *src, 0);
//
//	RenderEngine::set_render_target(dst, nullptr);
//	ModelManager::screen_aligned_quad.render();
//	RenderEngine::set_render_target(nullptr, nullptr);
//}

